'''
tag.py

Metadata tagging functions allowing nodes to be tagged with arbitrary tags and searched
for by tags.

Interface
    tagNodes(nodes, tags)    Tags node(s) with metadata tag(s)
    deleteTag(nodes, tags)   Removes a tag from a node.
    findTaggedNodes(tags)    Finds all nodes that have all specified tags.
    listTags(nodes)          Lists tags on a node or nodes.
    listAllTags()

Functions taking tags or nodes can be passed a list or a single item.  The way this
is implemented is that we check to see if the arg is a list and if it is, we re-call
the function with each element in the list.  First this is done with the nodes, then with
the tags.  The end result is that the function gets called once for each possible pair
of elements.

'''
# TODO: Doesn't support namespaces
# TODO: Needs support for imports with no namespaces.  A merge tags function...
# TODO: remove dependency on pymel
from pymel.all import *


_kNodeAttr = 'tags'
_kNodeAttrGroup = 'metadata'
_kTagAttr = 'taggedNodes'
_kTagNodeType = 'network'
_kTagPrefix = 'meta_'

def _createTagNode(nodeName):
    ''' Creates a tag node - unconditionally '''
    tagnode = createNode(_kTagNodeType, name=nodeName)
    tagnode.addAttr(_kTagAttr, at='message', multi=True, im=False)
    return tagnode
    
def _getTagNodeName(tagname):
    '''
    Returns a tag node name from the tag name.
    
    This should be the only function that depends on relationship between
    the name of the tag and the name of the node.
    '''
    return (_kTagPrefix + tagname)
    
def _getTagNode(tagName):
    '''
    Gets a tag node from the name, creating the tag node if necessary.
    
    If you only want the name and not the node itself, use _getTagNodeName().
    '''
    nodeName = _getTagNodeName(tagName)
    if not objExists(nodeName):
        tagNode = _createTagNode(nodeName)
    else:
        tagNode = PyNode(nodeName)
        assert tagNode.type() == _kTagNodeType
        assert tagNode.hasAttr(_kTagAttr)
    return tagNode

def _addTagAttributes(node):
    ''' DEPRECATED: Adds attributes to the node in preparation for being tagged '''
    assert type(node) == core.nodetypes.Transform
    
    if not node.hasAttr('metadata.tags'):
        node.addAttr('metadata', at='compound', numberOfChildren=1)
        node.addAttr('tags', at='message', multi=True, im=False, parent='metadata')
    
def _connectNode(tagnode, node):
    ''' Connects a tag with a node, creating the attrs on the target node if neccesary.'''
    assert type(tagnode) == core.nodetypes.Network
    assert type(node) == core.nodetypes.Transform

    if tagnode not in listConnections(node.message):
        connectAttr(node.message, tagnode.taggedNodes, na=True)

def tagNodes(nodes, tags):
    '''
    Tags node(s) with metadata tag(s)
    
    This function can be called with a list or single item for either argument.
    '''
    # Convert lists to singular items and re-call the function.
    if hasattr(nodes, "__iter__"):
        for node in nodes:
            tagNodes(node, tags)
        return

    # We're here, so nodes is really just a node (not a list).
    # Now tease out tags.
    if hasattr(tags, "__iter__"):
        for tag in tags:
            tagNodes(nodes, tag)
        return

    # We're here, so nodes and tags are both just a single node and single tag
    tagNode = _getTagNode(tags)
    _connectNode(PyNode(tagNode), PyNode(nodes))
    
def deleteTag(nodes, tags):
    '''
    Removes a tag from a node.
    '''
    if hasattr(nodes, "__iter__"):
        for node in nodes:
            deleteTag(node, tags)
        return

    # We're here, so nodes is really just a node
    if hasattr(tags, "__iter__"):
        for tag in tags:
            deleteTag(nodes, tag)
        return
        
    pnode = PyNode(nodes)
    ptag = PyNode(_getTagNodeName(tags))

    # We're here, so nodes and tags are both just a single node and single tag
    if pnode in listConnections(ptag.taggedNodes):
        # Odd, but if you don't want to/can't specify the index, na deletes
        # the first matching connection.
        disconnectAttr(ptag.taggedNodes, pnode.metadata.tags, na=True)
    
def findTaggedNodes(tags):
    '''
    Finds all nodes that have all specified tags.
    
    
    '''
    if hasattr(tags, "__iter__"):
        nodes = []
        for tag in tags:
            taggednodes = findTaggedNodes(tag)
            nodes = nodes + taggednodes
        return list(set(nodes))
    
    taggednodes = []
    
    # We have the get the name and check for the existence first because if
    # we just call _getTagNode(), we'll create tags for everything we check
    # for.
    tagnode = _getTagNodeName(tags)
    if not objExists(tagnode):
        return []
    
    # Could call _getTagNode() here, but this is easier because we know the
    # node exists.
    tagnode = PyNode(tagnode)
    for node in tagnode.taggedNodes.connections():
        taggednodes.append(str(node))
    return taggednodes

import maya.cmds as mc
def findTaggedNodesNoPM(tags):
    '''
    Finds all nodes that have all specified tags.
    
    
    '''
    '''
    if hasattr(tags, "__iter__"):
        nodes = []
        for tag in tags:
            taggednodes = findTaggedNodes(tag)
            nodes = nodes + taggednodes
        return list(set(nodes))
    '''
    taggednodes = []
    
    # We have the get the name and check for the existence first because if
    # we just call _getTagNode(), we'll create tags for everything we check
    # for.
    tagnode = _getTagNodeName(tags)
    #if not objExists(tagnode):
    #    return []
    
    # Could call _getTagNode() here, but this is easier because we know the
    # node exists.
    taggednodes = [node for node in mc.listConnections(tagnode)]
    return taggednodes

def listTags(nodes):
    '''
    Lists tags on a node or nodes.
    
    If a list of nodes are passed in, you get the union of tags on those nodes.
    '''
    if hasattr(nodes, "__iter__"):
        tags = []
        for node in nodes:
            nodetags = listTags(node)
            tags = tags + nodetags
        return list(set(tags))
    
    tags = []
    pnode = PyNode(nodes)
    for tag in pnode.message.connections():
        tags.append(str(tag[len(_kTagPrefix):]))
    return tags
    
def listAllTags():
    '''
    List all tags.
    
    Lists all the tags that have been created - even those that aren't currently
    applied to anything.
    '''
    tags = []
    tagnodes = ls(_kTagPrefix + '*', type=_kTagNodeType)
    for node in tagnodes:
        tags.append(str(node)[len(_kTagPrefix):])
    return tags
